/*
 *
 */
package com.training1.core.common.utils.pay.bohai.support;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.IOUtils;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.XMLUtils;
import org.apache.xpath.XPathAPI;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.training1.core.common.utils.pay.bohai.BoHaiUtil;

/**
 *  
 */
public class PacProcessThread extends Thread {
    private static final String charset = "UTF-8";

    private Socket socket = null;

    private PacKeyManager keyManager = null;

    private Logger log = BoHaiUtil.log;

    /**
     *  
     */
    public PacProcessThread(Socket aSocket, PacKeyManager pacKeyManager) {
        super();
        socket = aSocket;
        keyManager = pacKeyManager;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Runnable#run()
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
	public void run() {
        if (log.isDebugEnabled()) {
            log.debug("PacProcessThread run begin!");
        }

        try {
            if (null != socket) {

                ObjectInputStream ois = new ObjectInputStream(
                        socket.getInputStream());

                Map<String, Object> dataMap = (Map) ois.readObject();

                String transType = (String) dataMap.get("transType");

                String request = sign(PacUtil.formatData(dataMap), transType);

                String response = PacUtil.issuePac(request);

                log.info("Trans response:" + response);
                
                dataMap.put("response", response);

                boolean verifyFlag = false;

                if (null != response) {
                    verifyFlag = verify(response);
                    log.info("Verify result:"+verifyFlag);
                }
                
               
                String rspCode = null;
                String rspMsg = "";

                if (verifyFlag) {

                    String errorCode = null;
                    String errorMessage = "";

                    int pos1 = -1;
                    int pos2 = -1;
                    pos1 = response.indexOf("<rspCode>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</rspCode>");
                        rspCode = response.substring(pos1 + 9, pos2);
                    }

                    pos1 = response.indexOf("<rspMsg>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</rspMsg>");
                        rspMsg = response.substring(pos1 + 8, pos2);
                    }

                    if (null == rspCode) {
                        rspCode = "9";
                    }

                    dataMap.put("rspCode", rspCode);
                    dataMap.put("rspMsg", rspMsg);

                    pos1 = response.indexOf("<errorCode>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</errorCode>");
                        errorCode = response.substring(pos1 + 11, pos2);
                    }

                    pos1 = response.indexOf("<errorMessage>");
                    if (pos1 != -1) {
                        pos2 = response.indexOf("</errorMessage>");
                        errorMessage = response.substring(pos1 + 14, pos2);
                    }

                    if (null != errorCode) {
                        dataMap.put("errorCode", errorCode);
                        dataMap.put("errorMessage", errorMessage);
                    }
                } else {
                    dataMap.put("errorCode", "9999");
                    dataMap.put("errorMessage", "验证服务端签名失败");
                }

                ObjectOutputStream oos = new ObjectOutputStream(
                        socket.getOutputStream());
                oos.writeObject(dataMap);
                oos.flush();

            }
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug("send data error ", e);
            }
        }

        if (null != socket) {
            try {
                socket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            socket = null;
        }

        if (log.isDebugEnabled()) {
            log.debug("PacProcessThread run end!");
        }

    }

    private String sign(String xmlContent, String transType) {
        String xmlSign = null;

        try {
            log.info("Begin sign :" + transType + ", xml:" + xmlContent);

            Document doc = string2Doc(xmlContent, charset);

            XMLSignature sign = new XMLSignature(doc, "",
                    XMLSignature.ALGO_ID_SIGNATURE_RSA);

            sign.getSignedInfo().addResourceResolver(new OfflineResolver());

            Node messageNode = doc.getElementsByTagName("Message").item(0);
            messageNode.appendChild(sign.getElement());

            Transforms transforms = new Transforms(doc);
            transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);

            sign.addDocument("#" + transType + "Req", transforms,
                    org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);

            // ǩ��
            sign.sign(keyManager.getPrivateKey());

            xmlSign = doc2String(doc, charset);
        } catch (Exception e) {
            log.error("Error occur while sign !", e);
        }

        return xmlSign;
    }

    private boolean verify(String xml) {
        boolean flag = false;

        try {
            Document doc = string2Doc(xml, charset);

            Element nscontext = XMLUtils.createDSctx(doc, "ds",
                    org.apache.xml.security.utils.Constants.SignatureSpecNS);
            
            Element signElement = (Element) XPathAPI.selectSingleNode(doc,
                    "//ds:Signature[1]", nscontext);

            if (null != signElement) {
                XMLSignature signature = new XMLSignature(signElement, "");

                flag = signature.checkSignatureValue(keyManager
                        .getCertificate().getPublicKey());
            }
        } catch (Exception e) {
            log.error("Error occur while verify !", e);
        }

        return flag;
    }

    /**
     * @param xml
     * @return
     * @throws Exception
     */
    private Document string2Doc(String xml, String charset) throws Exception {

        InputStream is = IOUtils.toInputStream(xml, charset);

        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
                .newInstance();
        docBuilderFactory.setNamespaceAware(true);
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

        Document doc = null;
        if (null != charset) {
            doc = docBuilder.parse(is, charset);
        } else {
            doc = docBuilder.parse(is);
        }
        return doc;
    }

    /**
     * @param doc
     * @return
     * @throws Exception
     */
    private String doc2String(Document doc, String charset) throws Exception {

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();
        if (null != charset) {
            t.setOutputProperty("encoding", charset);
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        t.transform(new DOMSource(doc), new StreamResult(bos));
        String xmlStr = null;

        if (null != charset) {
            xmlStr = bos.toString(charset);
        } else {
            xmlStr = bos.toString();
        }
        return xmlStr;
    }

}